/* 2012/4/18 */
using System;
using System.Text;

namespace Cosmos.Security.Cryptography
{
    /// <summary>
    /// Represents a group of hash values including MD5, SHA1, CRC32.
    /// </summary>
    public class HashValue : IEquatable<HashValue>
    {
        private const int ByteLength = 8;

        private byte[] _md5;
        private byte[] _sha1;
        private byte[] _crc32;
        private int _hashCode;

        /// <summary>
        /// Initializes an instance of HashValue class.
        /// </summary>
        /// <param name="md5">MD5 value.</param>
        /// <param name="sha1">SHA1 value.</param>
        /// <param name="crc32">CRC32 value.</param>
        internal HashValue(byte[] md5, byte[] sha1, byte[] crc32)
        {
            _md5 = md5;
            _sha1 = sha1;
            _crc32 = crc32;

            _hashCode = 0;
            for (int i = 0; i < _crc32.Length; i++)
            {
                _hashCode <<= ByteLength;
                _hashCode |= _crc32[i];
            }
        }        

        /// <summary>
        /// Determines whether two specified HashValue objects have the same value.
        /// </summary>
        /// <param name="a">The first instance of HashValue class to compare, or null.</param>
        /// <param name="b">The second instance of Hashvalue class to compare, or null.</param>
        /// <returns>true if the value of a is the same as the value of b; otherwise, false.</returns>
        public static bool Equals(HashValue a, HashValue b)
        {
            if (((object)a) == null && ((object)b) == null)
                return true;

            if (((object)a) == null || ((object)b) == null)
                return false;

            if (a._md5.Length != b._md5.Length
                || a._sha1.Length != b._sha1.Length
                || a._crc32.Length != b._crc32.Length)
                return false;

            int md5Count = a._md5.Length;
            for (int i = 0; i < md5Count; i++)
            {
                if (a._md5[i] != b._md5[i])
                    return false;
            }

            int sha1Count = a._sha1.Length;
            for (int i = 0; i < sha1Count; i++)
            {
                if (a._sha1[i] != b._sha1[i])
                    return false;
            }

            int crc32Count = a._crc32.Length;
            for (int i = 0; i < crc32Count; i++)
            {
                if (a._crc32[i] != b._crc32[i])
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Determines whether two specified HashValue objects have the same value.
        /// </summary>
        /// <param name="a">The first instance of HashValue class to compare, or null.</param>
        /// <param name="b">The second instance of Hashvalue class to compare, or null.</param>
        /// <returns>true if the value of a is the same as the value of b; otherwise, false.</returns>
        public static bool operator ==(HashValue a, HashValue b)
        {
            return Equals(a, b);
        }

        /// <summary>
        /// Determines whether two specified HashValue objects have different values.
        /// </summary>
        /// <param name="a">The first instance of HashValue class to compare, or null.</param>
        /// <param name="b">The second instance of Hashvalue class to compare, or null.</param>
        /// <returns>true if the value of a is different from the value of b; otherwise, false.</returns>
        public static bool operator !=(HashValue a, HashValue b)
        {
            return !Equals(a, b);
        }

        /// <summary>
        /// Converts a byte array to a string in which every byte is represented as a hexadecimal.
        /// </summary>
        /// <param name="text">A byte array.</param>
        /// <returns>A string in which every byte is represented as a hexadecimal.</returns>
        private static string ByteToHex(byte[] text)
        {
            StringBuilder strBuilder = new StringBuilder(text.Length * 2);
            foreach (byte aByte in text)
            {
                strBuilder.Append(aByte.ToString("x2").ToUpper());
            }

            return strBuilder.ToString();
        }

        /// <summary>
        /// Determines whether this instance and another instance of HashValue object have the same value.
        /// </summary>
        /// <param name="other">A instance of HashValue to compare to this instance.</param>
        /// <returns>true if the compared instance is the same as this instance; otherwise, false.</returns>
        public bool Equals(HashValue other)
        {
            return Equals(this, other);
        }

        /// <summary>
        /// Determines whether this instance and a specified object,
        /// which must also be a HashValue object, have the same value.
        /// </summary>
        /// <param name="obj">A object to compare to this instance.</param>
        /// <returns>true if the compared object is the same as this instance; otherwise, false.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is HashValue))
            {
                return base.Equals(obj);
            }
            else
            {
                return Equals((HashValue)obj);
            }
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return _hashCode;
        }

        /// <summary>
        /// Gets a byte array which represents MD5 value of the file.
        /// </summary>
        public byte[] MD5Value
        {
            get 
            {
                return _md5;
            }
        }

        /// <summary>
        /// Gets a byte array which represents SHA1 value of the file.
        /// </summary>
        public byte[] SHA1Value
        {
            get
            {
                return _sha1; 
            }
        }

        /// <summary>
        /// Gets a byte array which represents CRC32 value of the file.
        /// </summary>
        public byte[] CRC32Value
        {
            get
            { 
                return _crc32;
            }
        }

        /// <summary>
        /// Gets a string which represents MD5 value of the file.
        /// </summary>
        public string MD5Text
        {
            get 
            {
                return ByteToHex(_md5); 
            }
        }

        /// <summary>
        /// Gets a string which represents SHA1 value of the file.
        /// </summary>
        public string SHA1Text
        {
            get
            {
                return ByteToHex(_sha1); 
            }
        }

        /// <summary>
        /// Gets a string which represents CRC32 value of the file.
        /// </summary>
        public string CRC32Text
        {
            get
            {
                return ByteToHex(_crc32); 
            }
        }

        /*
        private static char[] ByteToHex(byte[] text)
        {
            char[] index = new char[text.Length * 2];
            for (int i = 0; i < text.Length; i++)
            {
                index[i * 2] = Hexadecimal((text[i] & 0xF0) >> 4);

                index[i * 2 + 1] = Hexadecimal(text[i] & 0x0F);
            }
            return index;
        }

        private static char Hexadecimal(int digital)
        {
            char index;
            switch (digital)
            {
                case 0: index = '0'; break;
                case 1: index = '1'; break;
                case 2: index = '2'; break;
                case 3: index = '3'; break;
                case 4: index = '4'; break;
                case 5: index = '5'; break;
                case 6: index = '6'; break;
                case 7: index = '7'; break;
                case 8: index = '8'; break;
                case 9: index = '9'; break;
                case 10: index = 'a'; break;
                case 11: index = 'b'; break;
                case 12: index = 'c'; break;
                case 13: index = 'd'; break;
                case 14: index = 'e'; break;
                case 15: index = 'f'; break;
                default: index = '*'; break;
            }
            return index;
        }*/
    }
}
